package Config::System;

use Moose;
use Scalar::Defer;
use aliased 'Config::System::Store' => 'Store_Class';

our $Global_Store; # made as lazy as possible, not lexical so it can be localized
sub Store { $Global_Store}

# compile time -----------------------------------------------------------------

sub import {
    my ($class, @args) = @_;
    my $section_name = caller; # also configurable class name

    my $options = ref($args[0]) eq 'HASH'? shift(@args): {};
    $class->handle_options($section_name, %$options);

    while (@args) {
        my ($key, $descriptor) = (shift @args, shift @args);
        my $properties = ref($key) eq 'ARRAY'? $key: [$key];
        for my $property_name (@$properties) {
            Store->add_property($section_name => $property_name => $descriptor);
            $class->import_defered_var($section_name, $property_name);
        }
    }
}

sub import_defered_var {
    my ($class, $section_name, $name) = @_;
    no strict 'refs';
    my $value = defer { $class->get($section_name, $name) };
    *{"${section_name}::$name"} = \$value;
}

sub handle_options {
    my ($class, $section_name, %options) = @_;
    no strict 'refs';

    # true by default unless export_Store was given and is false
    my $should_export_Store =
        !exists($options{export_Store}) || $options{export_Store};
    *{"${section_name}::Store"} = \&Store if $should_export_Store;
    delete $options{export_Store};

    my @import = $options{import}? @{ $options{import} }: ();
    *{"${section_name}::$_"} = *{__PACKAGE__. "::$_"} for @import;
    delete $options{import};

    my $link = delete $options{link};
    $Global_Store ||= Store_Class->new(%options);

    if ($link) {
        $class->import_defered_var($section_name, $_)
            for Store->link($link, $section_name);
    }
}

# run time ---------------------------------------------------------------------

sub isolate_configuration (&;$) {
    my ($code, $store) = @_;
    # TODO optimize with copy on write
    local $Global_Store = $store || Store->clone;
    $code->();
}

sub get {
    my ($class, $section_name, $prop_name) = @_;
    return Store->get($section_name, $prop_name);
}


1;

__END__

#TODO use Data::OptList for special cases

# not a configurable class- just export the Store accessor
use Config::System;

# declare one configuration property, type Num and default value 8080
use Config::System port => {isa => 'Num', default => 8080};

# same, but no isa so type constraint is 'Any' and no default
use Config::System port => {};

# !NOT SUPPORTED YET!
# a shortcut, same as above
use Config::System 'port';

# three properties
use Config::System
    port    => {isa => 'Num'},
    timeout => {isa => 'Num'},
    host    => {isa => 'Str'};

# properties with same descriptor can be grouped, so the above can be written
use Config::System
    [qw(port timeout)] => {isa => 'Num'},
    host               => {isa => 'Str'};

# !NOT SUPPORTED YET!
# if you dont care about types
use Config::System qw(port host timeout);

# you can always add global options as a hash ref in the 1st element
use Config::System
    {
        paths        => [qw(../conf/networking.conf)],
        import       => [qw(isolate_configuration)],
        export_Store => 0,
    },
    port => {isa => 'Num', default => 8080};

# !NOT SUPPORTED YET!
# mix and match
use Config::System
    host => {isa => 'Str'};
    [qw(port timeout)],
    [qw(IP mask)] => {isa => 'Str'},
    'pid_file';

TODO: add more options: more config files

declare a class as configurable:
create a section subclass for the configurable class
export lazy values to the configurable class
when accessed, they are retrieved through the global policy object
which gets them from the correct store
each policy class models a different strategy to get the correct store

if 1st param is hash, then it is interperted as options
1) what helper subs to import
2) where are config files

